Top
Healenium: Unlock stability in Appium Test Automation - StaleElement
fade
4246
post-template-default,single,single-post,postid-4246,single-format-standard,eltd-core-1.2.1,flow child-child-ver-1.0.1,flow-ver-1.7,,eltd-smooth-page-transitions,ajax,eltd-grid-1300,eltd-blog-installed,page-template-blog-standard,eltd-header-vertical,eltd-sticky-header-on-scroll-up,eltd-default-mobile-header,eltd-sticky-up-mobile-header,eltd-dropdown-default,eltd-header-style-on-scroll,wpb-js-composer js-comp-ver-6.4.2,vc_responsive
Healenium: Unlock the stability in appium test automation

Healenium: Unlock stability in Appium Test Automation

Reading Time: 5 minutes

Appium, a powerful tool for mobile test automation, often grapples with the persistent issue of test flakiness. This instability arises from the dynamic nature of mobile apps, where UI elements frequently change, leading to test failures and increased maintenance overhead. To address these challenges, Healenium emerges as a revolutionary solution, which can significantly enhance the reliability and help us to unlock the stability in Appium test automation.

Understanding the Appium Challenge

Appium, Although is very versatile, faces hurdles in maintaining test stability due to:

  • Frequent UI changes: If Mobile app UI tends to change frequently sprint over sprint. Modifications in app layout or element attributes can render existing locators obsolete, causing test failures.
  • Maintenance overhead: Updating test scripts to accommodate new UI changes is a time consuming and error-prone process.

Introducing Healenium: A Self-Healing Solution

Healenium is an innovative open-source library that helps to addresses these challenge in Appium. It acts as a mediator between your test script and the Appium server, intelligently handling NoSuchElement test failures and adapting to dynamic UI changes.

Benefit of Healenium

  • Improved Stability: It employs a sophisticated mechanism to identify multiple potential locators for a UI element, increasing the chances of finding a valid match even if the primary locator fails.
  • Dynamic Element Handling: Healenium can dynamically adjust test interactions based on real-time UI changes, ensuring that test scripts remain resilient to modifications.
  • Minimize Maintenance: When a test encounters an error, Healenium can attempt to recover by performing alternative locators. This reduces the flakiness in test and result in less maintenance works.
  • Root Cause Analysis: Healenium provides detailed reports on test failures, including information about the attempted healing, screenshot and new locators that can help in root cause analysis and test improvement.
  • Plugin for Intellij IDE: Healenium provides plugin for Intellij IDE that can automatically update the broken locators in your codebase.

How Healenium Works

Healenium employs a combination of advanced algorithms and techniques to achieve its self-healing capabilities.
It offers two primary integration methods:

  1. Healenium-Proxy: A versatile, language-agnostic solution positioned between your test runner and the web application. It supports multiple languages and requires configuring your test to connect to the proxy as a RemoteWebDriver.
  2. Healenium-Web:Specifically for Java, this method integrates directly into your test code for a tighter coupling.

Both approaches leverage Healenium’s core capabilities: intelligent locator selection, dynamic element handling, self-healing actions, and detailed reporting.

Healenium working with appium test automation

As shown in above flow chart, Healenium comprises three core services:

  1. Healenium Backend: This central component processes healed locators, manages storage, handles screenshots and reports, and orchestrates communication between the other services.
  2. Healenium DB: A PostgreSQL database storing baseline locators, elements, and healed locators for subsequent runs.
  3. Healenium Selector Imitator: The core healing engine that reconstructs user-defined selectors based on HTML changes, suggesting potential alternatives while preserving the original selector structure.

Integrating Healenium with Appium

Pre-Requisites

  1. Docker: Installed and running on your system. As Healenium uses docker to run their self healing mechanism. To install Docker, you can follow the guide: here
  2. Appium: Set up for mobile test automation as Healenium integrates with Appium. You can refer here for instruction.
  3. Required language dependencies: Install necessary tools for your test automation framework (e.g., Java, Python, JavaScript).

Step By Step Guide

Step 1: Starting Healenium Proxy:

We can start Healenium proxy with docker-compose file that can start all the 3 services mentioned above and healenium proxy as well in the docker. But We recommend cloning the Healenium repository as it simplifies setup.

  • Use the following git command in your terminal to clone the repository.
Bash
git clone https://github.com/healenium/healenium.git
  • Once cloned, navigate to the root directory of the Healenium repository:
Bash
cd ./healenium

Healenium repository structure
  • These instructions assume you’re using a local Appium server. If not, update the docker-compose-appium.yaml file with the following line:
YAML
    - APPIUM_SERVER_URL=http://host.docker.internal:4723/wd/hub
  • Ensure your Docker desktop is running. Then, start Healenium Proxy using the following command:
Bash
docker-compose -f docker-compose-appium.yaml up -d
  • You’ll see following console logs indicating all four services have started.
Healenium: A Game-Changer for Appium Test Automation
  • Allow a few minutes for everything to be fully up and running.
Healenium services started in docker
  • To confirm Healenium Backend is operational, navigate to http://localhost:7878/ in your web browser. You should see the Healenium landing page.
Healenium dashboard

Step 2: Configuring Your Appium Driver

To route Appium driver requests through the Healenium proxy, replace the Appium server URL with the Healenium proxy’s address. This typically looks like following:

Java
// Use Healenium proxy URL instead of Appium server URL
WebDriver driver = new AndroidDriver(new URL("http://localhost:8085"), caps);

Step 3: Execution:

With Healenium Proxy running and the Appium driver configured, you’re ready to execute your first test.
You can use AppiumJava_BoilerPlate for POC.

Note: To enable Healenium’s self-healing, run a successful test initially to establish a baseline of locators.

After completing your test suite, visit http://localhost:7878/healenium/selectors/ to view the saved locators. These serve as a foundation for future healing attempts.

Healenium selectors baseline

To test Healenium’s self-healing capabilities, modify a locator in your app. For instance, change the “Log In” link to “Sign In” in your test app. Re-run the test.


Note: I’m using my-demo-app-android from SauceLabs for this demo.

Changing element attributes to test self healing in Appium test automation

Normally, this change would cause the test to fail. However, Healenium should identify the modified element and attempt to heal the locator.

Self healing in healenium

To review healed elements, navigate to http://localhost:7878/healenium/report and select your test run. This provides details about healed locators and accompanying screenshots.

Self healed report of Healenium

Conclusion

Healenium is a valuable tool that significantly reduces maintenance efforts for Appium test scripts, especially when dealing with minor UI changes like button text or element IDs. However, it’s essential to be aware of potential drawbacks.

For instance, Healenium’s self-healing capabilities might inadvertently mask UI changes that impact user experience. Let’s say If text of a “Cancel” button is replaced with the text of “Submit” button, Healenium could mistakenly heal the locator, potentially leading to unexpected application behavior.

To mitigate this risk, Healenium offers granular control over which elements should be self-healed. This feature ensures that critical UI changes are not overlooked while benefiting from the tool’s automation capabilities.

Yogendra Porwal

As an experienced Architect In Test Automation, I bring over 10 years of expertise in Quality Assurance across diverse software domains. With technical proficiency in multiple verticals of testing, including automation, functional, performance and security testing among others, my focus is on applying this knowledge to enhance the overall quality assurance process. I also like to go on long journeys with my bike , do sketching in my free time and have keen interest in video games.

4 Comments
  • Shilpa Patil

    Hi,
    Thanks for the valuable information. I am stuck at find element screen. Able to run test and redirect to provided browser, but then it fails on finding element.

    December 11, 2024 at 7:48 am Reply
    • Yogendra Porwal

      Hi Shilpa
      Can you please share debug logs from healenium and your tests.

      December 11, 2024 at 8:24 am Reply
  • Shilpa Patil

    Thank you in advance.
    Find logs from appium:

    [575bcee2][AndroidUiautomator2Driver@6959 (575bcee2)] Driver proxy active, passing request on via HTTP proxy
    [575bcee2][Chromedriver@dc30 (be119082)] Matched ‘/session/575bcee2-1d97-41a0-9b12-5e40ca70ba9d/url’ to command name ‘getUrl’
    [575bcee2][Chromedriver@dc30 (be119082)] Proxying [GET /session/575bcee2-1d97-41a0-9b12-5e40ca70ba9d/url] to [GET http://127.0.0.1:62512/session/be119082a48f58969dc39094b7de6122/url%5D with no body
    [575bcee2][Chromedriver@dc30 (be119082)] Got response with status 200: {“value”:”https://www.amazon.in/”}
    [575bcee2][HTTP] POST /wd/hub/session/575bcee2-1d97-41a0-9b12-5e40ca70ba9d/element {“using”:”css selector”,”value”:”input#nav-search-keywords.nav-input nav-progressive-attribute”}
    [575bcee2][HTTP] No route found for /wd/hub/session/575bcee2-1d97-41a0-9b12-5e40ca70ba9d/element
    [575bcee2][HTTP] DELETE /session/575bcee2-1d97-41a0-9b12-5e40ca70ba9d {}
    [575bcee2][AndroidUiautomator2Driver@6959 (575bcee2)] Calling AppiumDriver.deleteSession() with args: [“575bcee2-1d97-41a0-9b12-5e40ca70ba9d”]
    [575bcee2][AppiumDriver@a7c5] Event ‘quitSessionRequested’ logged at 1733907076901 (14:21:16 GMT+0530 (India Standard Time))
    [575bcee2][AppiumDriver@a7c5] Removing session 575bcee2-1d97-41a0-9b12-5e40ca70ba9d from our master session list
    [575bcee2][AndroidUiautomator2Driver@6959 (575bcee2)] Deleting UiAutomator2 session
    [575bcee2][AndroidUiautomator2Driver@6959 (575bcee2)] Stopping chromedriver for context CHROMIUM
    [575bcee2][Chromedriver@dc30 (be119082)] Changed state to ‘stopping’
    [575bcee2][Chromedriver@dc30 (be119082)] Proxying [DELETE /] to [DELETE http://127.0.0.1:62512/session/be119082a48f58969dc39094b7de6122%5D with no body
    [575bcee2][Chromedriver@dc30 (be119082)] Got response with status 200: {“value”:null}
    [575bcee2][Chromedriver@dc30] Changed state to ‘stopped’
    [575bcee2][AndroidUiautomator2Driver@6959 (575bcee2)] Deleting UiAutomator2 server session
    [575bcee2][AndroidUiautomator2Driver@6959 (575bcee2)] Matched ‘/’ to command name ‘deleteSession’
    [575bcee2][AndroidUiautomator2Driver@6959 (575bcee2)] Proxying [DELETE /] to [DELETE http://127.0.0.1:8200/session/80d5d3bb-4b07-4863-afad-0bce87eff99d%5D with no body
    [575bcee2][AndroidUiautomator2Driver@6959 (575bcee2)] Got response with status 200: {“sessionId”:”80d5d3bb-4b07-4863-afad-0bce87eff99d”,”value”:null}
    [575bcee2][ADB] Running ‘C:UsersShilpapatilAppDataLocalAndroidSdkplatform-toolsadb.exe -P 5037 -s RZ8N906JCBB shell dumpsys activity services io.appium.settings/.recorder.RecorderService’
    [575bcee2][Logcat] Stopping logcat capture
    ———————————————————————————————————————————————————————————————-Test Logs from IDE:

    https://www.amazon.in/
    org.openqa.selenium.NoSuchElementException: Failed to find element using By.cssSelector: input#nav-search-keywords.nav-input nav-progressive-attribute
    For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
    Build info: version: ‘4.11.0’, revision: ‘040bc5406b’
    System info: os.name: ‘Windows 11’, os.arch: ‘amd64’, os.version: ‘10.0’, java.version: ‘11.0.19’
    Driver info: io.appium.java_client.AppiumDriver
    Command: [575bcee2-1d97-41a0-9b12-5e40ca70ba9d, findElement {using=css selector, value=input#nav-search-keywords.nav-input nav-progressive-attribute}]
    Capabilities {appium:automationName: uiautomator2, appium:databaseEnabled: false, appium:desired: {appActivity: com.android.chrome/com.goog…, appPackage: com.android.chrome, automationName: uiautomator2, browserName: chrome, deviceName: emulatoe-5554, nativeWebScreenshot: true, platformName: ANDROID}, appium:deviceApiLevel: 31, appium:deviceManufacturer: samsung, appium:deviceModel: SM-M317F, appium:deviceName: RZ8N906JCBB, appium:deviceScreenDensity: 420, appium:deviceScreenSize: 1080×2400, appium:deviceUDID: RZ8N906JCBB, appium:javascriptEnabled: true, appium:locationContextEnabled: false, appium:nativeWebScreenshot: true, appium:networkConnectionEnabled: true, appium:pixelRatio: 2.625, appium:platformVersion: 12, appium:statBarHeight: 87, appium:takesScreenshot: true, appium:viewportRect: {height: 2313, left: 0, top: 87, width: 1080}, appium:warnings: {}, appium:webStorageEnabled: false, browserName: chrome, platformName: ANDROID}
    Session ID: 575bcee2-1d97-41a0-9b12-5e40ca70ba9d
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)

    December 11, 2024 at 8:54 am Reply
  • Shilpa Patil

    Error coming in docker- hlm-proxy:

    “using”: “accessibility id”,n “value”: “open menu”n }
    2024-12-13 14:35:52 2024-12-13 12:05:52.606 WARN 1 – [ttp-epoll-4] healenium : Error during findElement: org.openqa.selenium.UnsupportedCommandException: The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resourcen Build info: version: ‘4.25.0’, revision: ‘8a8aea2337’n System info: os.name: ‘Linux’, os.arch: ‘amd64’, os.version: ‘5.15.167.4-microsoft-standard-WSL2’, java.version: ‘22.0.2’n Driver info: io.appium.java_client.AppiumDrivern Command: [8c7893ac-36dd-463e-a778-00bc1b41ed9b, findElement {using=accessibility id, value=open menu}]n Capabilities {appium:appActivity: com.saucelabs.mydemoapp.rn…., appium:appPackage: com.saucelabs.mydemoapp.rn, appium:automationName: uiautomator2, appium:databaseEnabled: false, appium:desired: {appActivity: com.saucelabs.mydemoapp.rn…., appPackage: com.saucelabs.mydemoapp.rn, automationName: uiautomator2, deviceName: RZ8N906JCBB, newCommandTimeout: 20, noReset: false, platformName: ANDROID, platformVersion: 12}, appium:deviceApiLevel: 31, appium:deviceManufacturer: samsung, appium:deviceModel: SM-M317F, appium:deviceName: RZ8N906JCBB, appium:deviceScreenDensity: 420, appium:deviceScreenSize: 1080×2400, appium:deviceUDID: RZ8N906JCBB, appium:javascriptEnabled: true, appium:locationContextEnabled: false, appium:networkConnectionEnabled: true, appium:newCommandTimeout: 20, appium:noReset: false, appium:pixelRatio: 2.625, appium:platformVersion: 12, appium:statBarHeight: 87, appium:takesScreenshot: true, appium:viewportRect: {height: 2313, left: 0, top: 87, width: 1080}, appium:warnings: {}, appium:webStorageEnabled: false, platformName: ANDROID}n Session ID: 8c7893ac-36dd-463e-a778-00bc1b41ed9bn at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)n at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)n at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)n at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:167)n at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:138)n at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:50)n at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:190)n at com.epam.healenium.healenium_proxy.command.HealeniumCommandExecutor.execute(HealeniumCommandExecutor.java:31)n at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:545)n at org.openqa.selenium.remote.ElementLocation$ElementFinder$2.findElement(ElementLocation.java:165)n at org.openqa.selenium.remote.ElementLocation.findElement(ElementLocation.java:66)n at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:368)n at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:362)n at com.epam.healenium.processor.FindElementProcessor.execute(FindElementProcessor.java:23)n at com.epam.healenium.processor.BaseProcessor.process(BaseProcessor.java:42)n at com.epam.healenium.handlers.proxy.BaseHandler.findElement(BaseHandler.java:63)n at com.epam.healenium.healenium_proxy.filter.FindElementRequestGatewayFilterFactory.findElement(FindElementRequestGatewayFilterFactory.java:62)n at com.epam.healenium.healenium_proxy.filter.FindElementRequestGatewayFilterFactory.lambda$apply$0(FindElementRequestGatewayFilterFactory.java:48)n at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:132)n at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)n at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299)n at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)n at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2097)n at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145)n at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144)n at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)n at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144)n at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:415)n at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:445)n at reactor.netty.http.server.HttpServerOperations.handleLastHttpContent(HttpServerOperations.java:862)n at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:784)n at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:115)n at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)n at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)n at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)n at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:311)n at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)n at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)n at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)n at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)n at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)n at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)n at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)n at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)n at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)n at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)n at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407)n at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)n at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)n at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)n at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:799)n at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:501)n at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:399)n at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994)n at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)n at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)n at java.base/java.lang.Thread.run(Thread.java:1570)n

    December 13, 2024 at 9:08 am Reply

Post a Comment